#pragma once

#include "Thread.hpp"
#include "LockGuard.hpp"
#include <vector>
#include <mutex>
#include <memory>
#include <queue>

const int gnum = 3;

template <class T>
class ThreadPool;

// 用来传递线程池指针和线程名字
template <class T>
class ThreadData
{
public:
    ThreadPool<T> *_tp;
    std::string _threadName;

public:
    ThreadData(ThreadPool<T> *tp, std::string threadName)
        : _tp(tp), _threadName(threadName)
    {
    }
};

template <class T> // T为任务类型
class ThreadPool
{
private:
    static void *handlerTask(void *args)
    {
        T t; // 存放获取的任务
        // 没有this指针,需要靠传参+强转
        ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
        ThreadPool<T> *tp = td->_tp; // 获得线程池指针
        while (1)
        {
            {
                LockGuard lockguard(&tp->_mutex); // RAII
                // 到此，已访任务队列，如果空就条件等待
                while ((tp->_taskQueue).empty())
                {
                    pthread_cond_wait(&tp->_cond, &tp->_mutex);
                }

                t = (tp->_taskQueue).front();
                (tp->_taskQueue).pop();
                // 获取任务，本质是将任务从公共队列中拿到当前线程自己独立的栈中，
                // 可以解锁，让该线程执行任务的同时，运行其他线程访问临界资源
            }
            std::cout << td->_threadName << "接收任务：" << t.taskToString() << "，执行任务，结果：" << t() << std::endl;
        }
        delete tp;
        delete td;
        return nullptr;
    }

    ThreadPool(const int &num = gnum)
        : _num(num)
    {
        // 互斥量、条件变量的初始化
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);

        // 创建新线程
        for (int i = 0; i < _num; i++)
        {
            Thread *td = new Thread();
            _threads.push_back(td);
        }
    }

    ThreadPool(const ThreadPool<T> &) = delete;
    ThreadPool &operator=(const ThreadPool<T> &) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        if (_pTP == nullptr)
        {
            std::unique_lock<std::mutex> lock(_mTP);    //这里使用了C++11的lock_guard
            if (_pTP == nullptr)
                _pTP = new ThreadPool<T>();
        }

        return _pTP;
    }

    // 对外接口，让别人往任务队列中放任务
    void push(const T &in)
    {
        // 访问线程池，加锁
        LockGuard lockguard(&_mutex);
        _taskQueue.push(in);
        // 到此，肯定有任务，任务队列不为空，需唤醒条件等待
        pthread_cond_signal(&_cond);
    }

    // 启动线程池，启动新线程
    void run()
    {
        for (const auto &t : _threads)
        {
            // Thread类采用在start时才传线程函数
            // 线程函数即任务，从任务队列中获取，
            // 而线程函数没有this指针，要把线程池的this指针作为参数传过去，让线程函数通过强转能够访问任务队列获取任务
            // 而这里又想把线程的具体名称传过去，故封装了一个简单的ThreadData类
            ThreadData<T> *td = new ThreadData<T>(this, t->threadName());
            t->start(handlerTask, td);
            std::cout << t->threadName() << "start..." << std::endl;
        }
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for (const auto &t : _threads)
            delete t;
    }

private:
    int _num;                       // 新线程数量
    std::vector<Thread *> _threads; // 通过vector来维护线程
    std::queue<T> _taskQueue;       // 任务队列
    pthread_mutex_t _mutex;         // 互斥，保护_taskQueue，仅允许一个线程同时访问，发布或获取任务
    pthread_cond_t _cond;           // 同步，这里主要限制消费者

    static ThreadPool<T> *_pTP;
    static std::mutex _mTP;     //这里用了C++11中的互斥量   ，这个锁是保护单例创建接口的
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_pTP;

template <class T>
std::mutex ThreadPool<T>::_mTP;